// Copyright 2016 Proyectos y Sistemas de Mantenimiento SL (eProsima).
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------
// Modification history:
// feature: support config uds locator by xml
// feature: provide query interfaces
// feature: content filter topic
// feature: Unified Transport Abstraction Layer Unified Transport Abstraction Layer
// feature: discovery support client and server
// feature: add statictic lookup
// ------------------------------------------------------------------

#include <edds/rtps/builtin/BuiltinProtocols.h>

#include <ertps/utils/LocatorAlias.h>

#include <discovery/participant/PDPSimple.h>
#include <discovery/participant/PDPClient.h>
#include <discovery/participant/PDPServer.hpp>
#include <discovery/endpoint/EDP.h>
#include <discovery/endpoint/EDPStatic.h>

#include <edds/rtps/builtin/data/ParticipantProxyData.h>

#include <edds/rtps/builtin/liveliness/WLP.h>

#include <rtps/participant/RTPSParticipantImpl.h>

#include <deps/common/IPFinder.h>

#include <algorithm>

#include <edds/dds/builtin/lookup/LookupManager.hpp>
#include <elog/Log.hpp>
#include <string>

using namespace evbs::ertps;

namespace evbs {
namespace ertps {
namespace rtps {

BuiltinProtocols::BuiltinProtocols() : mp_participantImpl(nullptr), mp_PDP(nullptr), mp_WLP(nullptr), tlm_(nullptr) {}

BuiltinProtocols::~BuiltinProtocols() {
    // Send participant is disposed
    if (mp_PDP != nullptr) {
        mp_PDP->announceParticipantState(true, true);
    }

    // TODO Auto-generated destructor stub
    delete mp_WLP;
    if (tlm_ != nullptr) {
        delete tlm_;
    }
    delete mp_PDP;
}

bool BuiltinProtocols::initBuiltinProtocols(RTPSParticipantImpl* p_part, BuiltinAttributes& attributes) {
    mp_participantImpl = p_part;
    m_att = attributes;
    m_metatrafficUnicastLocatorList = m_att.metatrafficUnicastLocatorList;
    m_metatrafficMulticastLocatorList = m_att.metatrafficMulticastLocatorList;
    m_initialPeersList = m_att.initialPeersList;

    {
        std::unique_lock<vbs::shared_mutex> disc_lock(getDiscoveryMutex());
        m_DiscoveryServers = m_att.discovery_config.m_DiscoveryServers;
    }

    filter_server_remote_locators();

    const RTPSParticipantAllocationAttributes& allocation = p_part->getRTPSParticipantAttributes().allocation;

    // PDP
    switch (m_att.discovery_config.discoveryProtocol) {
        case DiscoveryProtocol_t::NONE:
            logWarning(RTPS_PDP, "No participant discovery protocol specified");
            return true;

        case DiscoveryProtocol_t::SIMPLE:
            try {
                mp_PDP = new PDPSimple(this, allocation);
            } catch (const foonathan::memory::out_of_memory& e) {  //LCOV_EXCL_START
                logWarning(RTPS_PDP, std::string("catch out_of_memory exception:") << e.what());
                return false;
            }  //LCOV_EXCL_STOP
            break;

        case DiscoveryProtocol_t::EXTERNAL:
            elogError(RTPS_PDP, RetCode_t::RETCODE_ERROR, "Flag only present for debugging purposes");
            return false;

        case DiscoveryProtocol_t::CLIENT:
            mp_PDP = new PDPClient(this, allocation);
            break;

        case DiscoveryProtocol_t::SERVER:
            mp_PDP = new PDPServer(this, allocation, DurabilityKind_t::TRANSIENT_LOCAL);
            break;

#if HAVE_SQLITE3
        case DiscoveryProtocol_t::BACKUP:
            mp_PDP = new PDPServer(this, allocation, DurabilityKind_t::TRANSIENT);
            break;
#endif  // if HAVE_SQLITE3

        case DiscoveryProtocol_t::SUPER_CLIENT:
            mp_PDP = new PDPClient(this, allocation, true);
            break;

        default:  //LCOV_EXCL_START
            elogError(RTPS_PDP, RetCode_t::RETCODE_UNSUPPORTED, "Unknown DiscoveryProtocol_t specified.");
            return false;
    }  //LCOV_EXCL_STOP

    if (!mp_PDP->init(mp_participantImpl)) {
        elogError(RTPS_PDP, RetCode_t::RETCODE_ERROR, "Participant discovery configuration failed");
        delete mp_PDP;
        mp_PDP = nullptr;
        return false;
    }

    // WLP
    if (m_att.use_WriterLivelinessProtocol) {
        mp_WLP = new WLP(this);
        (void)mp_WLP->initWL(mp_participantImpl);
    }

    // LookupManager
    if (m_att.lookup_config.use_client || m_att.lookup_config.use_server) {
        tlm_ = new edds::dds::builtin::LookupManager(this);
        (void)tlm_->init_lookup_service(mp_participantImpl);
    }
    return true;
}

void BuiltinProtocols::enable() {
    if (nullptr != mp_PDP) {
        (void)mp_PDP->enable();
    }
}

void BuiltinProtocols::enable_second() {
    if (nullptr != mp_PDP) {
        mp_PDP->announceParticipantState(true);
        mp_PDP->resetParticipantAnnouncement();
    }
}

bool BuiltinProtocols::updateMetatrafficLocators(LocatorList_t& loclist) {
    m_metatrafficUnicastLocatorList = loclist;
    return true;
}

EvbsVersion_t BuiltinProtocols::get_version_by_guid(const GuidPrefix_t& guid_prefix) {
    if (mp_PDP) {
        return mp_PDP->get_version_by_guid(guid_prefix);
    }
    return evbs::edds::rtps::c_Version_vbs_unknown;
}

void BuiltinProtocols::filter_server_remote_locators() {
    vbs::shared_lock<vbs::shared_mutex> disc_lock(getDiscoveryMutex());

    LocatorList_t allowed_locators;

    for (auto loc : m_DiscoveryServers) {
        //if (nf.is_locator_remote_or_allowed(loc))

        if (!mp_participantImpl->network_factory()->is_local_locator(mp_participantImpl->get_transport_name(), loc) ||
            mp_participantImpl->network_factory()->is_locator_allowed(mp_participantImpl->get_transport_name(), loc)) {
            allowed_locators.push_back(loc);
        } else {
            elogWarning(RTPS_PDP, "Ignoring remote server locator " << loc << " : not allowed.");
        }
    }
    m_DiscoveryServers.swap(allowed_locators);
}

bool BuiltinProtocols::addLocalWriter(RTPSWriter* w, const ertps::TopicAttributes& topicAtt,
                                      const ertps::WriterQos& wqos, const vbsutil::xmlparser::ContentFilterProperty*) {
    bool ok = true;

    if (mp_PDP != nullptr) {
        ok = mp_PDP->getEDP()->newLocalWriterProxyData(w, topicAtt, wqos);

        if (!ok) {
            logWarning(RTPS_EDP, "Failed register WriterProxyData in EDP");
            return false;
        }
    } else {
        logWarning(RTPS_EDP, "EDP is not used in this Participant, register a Writer is impossible");
    }

    if (mp_WLP != nullptr) {
        ok &= mp_WLP->add_local_writer(w, wqos);
    } else {
        logWarning(RTPS_LIVELINESS, "LIVELINESS is not used in this Participant, register a Writer is impossible");
    }
    return ok;
}

bool BuiltinProtocols::addLocalReader(RTPSReader* R, const ertps::TopicAttributes& topicAtt,
                                      const ertps::ReaderQos& rqos,
                                      const vbsutil::xmlparser::ContentFilterProperty* content_filter) {
    bool ok = true;

    if (mp_PDP != nullptr) {
        ok = mp_PDP->getEDP()->newLocalReaderProxyData(R, topicAtt, rqos, content_filter);

        if (!ok) {
            logWarning(RTPS_EDP, "Failed register ReaderProxyData in EDP");
            return false;
        }
    } else {
        logWarning(RTPS_EDP, "EDP is not used in this Participant, register a Reader is impossible");
    }

    if (mp_WLP != nullptr) {
        ok &= mp_WLP->add_local_reader(R, rqos);
    }

    return ok;
}

bool BuiltinProtocols::updateLocalWriter(RTPSWriter* W, const TopicAttributes& topicAtt, const WriterQos& wqos,
                                         const vbsutil::xmlparser::ContentFilterProperty* content_filter, bool notify) {
    bool ok = false;
    if ((mp_PDP != nullptr) && (mp_PDP->getEDP() != nullptr)) {
        ok = mp_PDP->getEDP()->updatedLocalWriter(W, topicAtt, wqos, content_filter, notify);
    }
    return ok;
}

bool BuiltinProtocols::updateLocalReader(RTPSReader* R, const TopicAttributes& topicAtt, const ReaderQos& rqos,
                                         const ContentFilterProperty* content_filter, bool notify) {
    bool ok = false;
    if ((mp_PDP != nullptr) && (mp_PDP->getEDP() != nullptr)) {
        ok = mp_PDP->getEDP()->updatedLocalReader(R, topicAtt, rqos, content_filter, notify);
    }
    return ok;
}

bool BuiltinProtocols::removeLocalWriter(RTPSWriter* W) {
    bool ok = false;
    if (mp_WLP != nullptr) {
        ok |= mp_WLP->remove_local_writer(W);
    }
    if ((mp_PDP != nullptr) && (mp_PDP->getEDP() != nullptr)) {
        ok |= mp_PDP->getEDP()->removeLocalWriter(W);
    }
    return ok;
}

bool BuiltinProtocols::removeLocalReader(RTPSReader* R) {
    bool ok = false;
    if (mp_WLP != nullptr) {
        ok |= mp_WLP->remove_local_reader(R);
    }
    if ((mp_PDP != nullptr) && (mp_PDP->getEDP() != nullptr)) {
        ok |= mp_PDP->getEDP()->removeLocalReader(R);
    }
    return ok;
}

void BuiltinProtocols::announceRTPSParticipantState() {
    assert(mp_PDP);

    if (mp_PDP) {
        mp_PDP->announceParticipantState(false);
    } else if (m_att.discovery_config.discoveryProtocol != DiscoveryProtocol_t::NONE) {  //LCOV_EXCL_START
        elogError(RTPS_EDP, RetCode_t::RETCODE_PRECONDITION_NOT_MET,
                  "Trying to use BuiltinProtocols interfaces before initBuiltinProtocols call");
    }  //LCOV_EXCL_STOP
}

void BuiltinProtocols::stopRTPSParticipantAnnouncement() {
    // note that participants created with DiscoveryProtocol::NONE
    // may not have mp_PDP available

    if (mp_PDP) {
        mp_PDP->stopParticipantAnnouncement();
    } else if (m_att.discovery_config.discoveryProtocol != DiscoveryProtocol_t::NONE) {  //LCOV_EXCL_START
        elogError(RTPS_EDP, RetCode_t::RETCODE_PRECONDITION_NOT_MET,
                  "Trying to use BuiltinProtocols interfaces before initBuiltinProtocols call");
    }  //LCOV_EXCL_STOP
}

void BuiltinProtocols::resetRTPSParticipantAnnouncement() {
    assert(mp_PDP);

    if (mp_PDP) {
        mp_PDP->resetParticipantAnnouncement();
    } else if (m_att.discovery_config.discoveryProtocol != DiscoveryProtocol_t::NONE) {  //LCOV_EXCL_START
        elogError(RTPS_EDP, RetCode_t::RETCODE_PRECONDITION_NOT_MET,
                  "Trying to use BuiltinProtocols interfaces before initBuiltinProtocols call");
    }  //LCOV_EXCL_STOP
}

bool BuiltinProtocols::startRTPSParticipantInitialAnnouncement(uint32_t count, Duration_t period) {
    assert(mp_PDP);

    if (mp_PDP) {
        mp_PDP->start_initial_announcement(count, period);
        mp_PDP->stopParticipantAnnouncement();
        mp_PDP->resetParticipantAnnouncement();
    }
    return true;
}

}  // namespace rtps
}  // namespace ertps
} /* namespace evbs */
